using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using Microsoft.SharePoint;
using SharePoint.DataMapper;

namespace SharePoint.DataMapper.Metal
{
	public class CodeFileGenerator
	{
		public SPList List { get; private set; }

		public HashSet<string> Fields { get; private set; }

		public CodeFileGenerator(SPList list)
		{
			List = list;
			Fields = new HashSet<string>();
		}

		public void Generate()
		{
			Fields.Clear();
			Fields.Add(SPBuiltInFieldId.ContentType.ToString());
			Fields.Add(SPBuiltInFieldId.Title.ToString());

			CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
			CodeCompileUnit compileUnit = new CodeCompileUnit();
			CodeGeneratorOptions options = new CodeGeneratorOptions
			                               	{
			                               		BlankLinesBetweenMembers = false,
			                               		BracingStyle = "C",
			                               		VerbatimOrder = false,
			                               	};
			CodeNamespace namespace2 = new CodeNamespace();
			//CodeNamespace namespace3 = new CodeNamespace(this.File.Context.Namespace);
			namespace2.Imports.Add(new CodeNamespaceImport("System"));
			namespace2.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
			namespace2.Imports.Add(new CodeNamespaceImport("Microsoft.SharePoint"));
			namespace2.Imports.Add(new CodeNamespaceImport("SharePoint.DataMapper"));
			compileUnit.Namespaces.Add(namespace2);

			GenerateClass(namespace2, List);

			StreamWriter writer = File.CreateText(XmlConvert.EncodeName(List.Title) + ".cs");
			provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
			writer.Flush();
			writer.Close();
		}

		private void GenerateClass(CodeNamespace root, SPList list)
		{
			foreach (SPContentType contentType in list.ContentTypes)
			{
				GenerateInterface(root, contentType);
			}

			CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(XmlConvert.EncodeName(List.Title))
			                                          	{
			                                          		Attributes = MemberAttributes.Public,
			                                          		IsPartial = false,
			                                          		IsClass = true
			                                          	};
			
			codeTypeDeclaration.BaseTypes.Add("GenericItem");

			ListAttribute attribute = Helpers.GetListAttribute(list);
			GenerateAttributeDeclaration(codeTypeDeclaration.CustomAttributes, attribute, new ListAttribute(string.Empty));

			foreach (CodeTypeDeclaration typeDeclaration in root.Types)
			{
				if (typeDeclaration.IsInterface)
				{
					codeTypeDeclaration.BaseTypes.Add(typeDeclaration.Name);
				}

				foreach (CodeTypeMember member in typeDeclaration.Members)
				{
					if (member is CodeMemberProperty)
					{
						if (Fields.Contains(member.Name))
						{
							continue;
						}
						CodeMemberProperty property = GenerateProperty(codeTypeDeclaration, member.Name, ((CodeMemberProperty)member).Type);
						GenerateFieldForProperty(codeTypeDeclaration, property);
						Fields.Add(member.Name);
					}
				}
			}

			foreach (SPField field in list.Fields)
			{
				if (Fields.Contains(field.InternalName) || Fields.Contains(field.Id.ToString()))
				{
					continue;
				}
				CodeTypeReference typeReference = GetFieldValueTypeReference(root, field);
				if (!field.FromBaseType && field.Type != SPFieldType.Computed && field.Type != SPFieldType.Attachments && typeReference != null)
				{
					GenerateFieldAndProperty(codeTypeDeclaration, field, typeReference);
					Fields.Add(field.Id.ToString());
				}
			}

			root.Types.Add(codeTypeDeclaration);
		}

		private void GenerateInterface(CodeNamespace root, SPContentType contentType)
		{
			SPWeb web = contentType.ParentWeb;
			SPContentType parent = web.AvailableContentTypes[web.AvailableContentTypes.BestMatch(contentType.Id)];

			CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration("I" + XmlConvert.EncodeName(parent.Name))
			                                          	{
			                                          		Attributes = MemberAttributes.Public,
			                                          		IsPartial = false,
			                                          		IsClass = false,
			                                          		IsInterface = true
			                                          	};
			codeTypeDeclaration.BaseTypes.Add("IGenericItem");

			ContentTypeAttribute attribute = Helpers.GetContentTypeAttribute(parent);
			GenerateAttributeDeclaration(codeTypeDeclaration.CustomAttributes, attribute, new ContentTypeAttribute(string.Empty, "0x"));

			foreach (SPField field in parent.Fields)
			{
				if (field.Id == SPBuiltInFieldId.ContentType || field.Id == SPBuiltInFieldId.Title)
				{
					continue;
				}
				CodeTypeReference typeReference = GetFieldValueTypeReference(root, field);
				if (field.Type != SPFieldType.Computed && field.Type != SPFieldType.Attachments && typeReference != null)
				{
					GenerateFieldAndProperty(codeTypeDeclaration, field, typeReference);
					Fields.Add(field.Id.ToString());
				}
			}
			root.Types.Add(codeTypeDeclaration);
		}

		private CodeTypeDeclaration GenerateEnum(CodeNamespace root, SPFieldMultiChoice field)
		{
			CodeTypeDeclaration codeTypeDeclaration = new CodeTypeDeclaration(field.InternalName)
			                                          	{
			                                          		Attributes = MemberAttributes.Public,
			                                          		IsEnum = true, 
			                                          	};

			foreach (string s in field.Choices)
			{
				CodeMemberField codeMemberField = new CodeMemberField();
				codeMemberField.Name = XmlConvert.EncodeName(s);
				GenerateAttributeDeclaration(codeMemberField.CustomAttributes, new ChoiceAttribute(s), new ChoiceAttribute(string.Empty));
				codeTypeDeclaration.Members.Add(codeMemberField);
			}
			root.Types.Add(codeTypeDeclaration);
			return codeTypeDeclaration;
		}


		private CodeMemberField GenerateFieldForProperty(CodeTypeDeclaration root, CodeMemberProperty property)
		{
			CodeMemberField codeMemberField = new CodeMemberField();
			codeMemberField.Attributes = MemberAttributes.Private;
			codeMemberField.Name = "_" + property.Name.Substring(0, 1).ToLower() + property.Name.Substring(1);
			codeMemberField.Type = property.Type;
			root.Members.Add(codeMemberField);

			CodeFieldReferenceExpression codeFieldReferenceExpression = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), codeMemberField.Name);
			property.GetStatements.Add(new CodeMethodReturnStatement(codeFieldReferenceExpression));
			property.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), codeMemberField.Name), new CodePropertySetValueReferenceExpression()));
			return codeMemberField;
		}

		private void GenerateFieldAndProperty(CodeTypeDeclaration root, SPField field, CodeTypeReference typeReference)
		{
			CodeMemberProperty codeMemberProperty = GenerateProperty(root, field.InternalName, typeReference);
			GenerateFieldForProperty(root, codeMemberProperty);
			
			FieldAttribute attribute = FieldManager.Resolve(field.Type).GetFieldAttribute(field);
			GenerateAttributeDeclaration(codeMemberProperty.CustomAttributes, attribute, new FieldAttribute(string.Empty, SPFieldType.Invalid));
		}

		private CodeMemberProperty GenerateProperty(CodeTypeDeclaration root, string name, CodeTypeReference typeReference)
		{
			CodeMemberProperty codeMemberProperty = new CodeMemberProperty();
			codeMemberProperty.Attributes = MemberAttributes.Final | MemberAttributes.FamilyAndAssembly | MemberAttributes.FamilyOrAssembly;
			codeMemberProperty.HasGet = true;
			codeMemberProperty.HasSet = true;
			codeMemberProperty.Name = name;
			codeMemberProperty.Type = typeReference;
			root.Members.Add(codeMemberProperty);
			return codeMemberProperty;
		}

		public CodeTypeReference CreateTypeReference(Type type, params string[] args)
		{
			CodeTypeReference codeTypeReference = new CodeTypeReference(type);
			string[] sArr = args;
			for (int i = 0; i < sArr.Length; i++)
			{
				string s = sArr[i];
				codeTypeReference.TypeArguments.Add(new CodeTypeReference(s, CodeTypeReferenceOptions.GenericTypeParameter));
			}
			return codeTypeReference;
		}

		public CodeAttributeDeclaration GenerateAttributeDeclaration<T>(CodeAttributeDeclarationCollection collection, T attribute, T defaultAttribute)
			where T : Attribute
		{
			Type attributeType = attribute.GetType();

			List<CodeAttributeArgument> arguments = new List<CodeAttributeArgument>();
			foreach (PropertyInfo propertyInfo in attributeType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
			{
				object value = propertyInfo.GetValue(attribute, new object[0]);
				if (value == null)
				{
					continue;
				}
				if (propertyInfo.CanWrite && value.Equals(propertyInfo.GetValue(defaultAttribute, new object[0])))
				{
					continue;
				}
				CodeAttributeArgument argument = CreateCodeAttributeArgument(attribute, propertyInfo, value);
				if (argument != null)
				{
					arguments.Add(argument);
				}
			}
			CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(CreateTypeReference(attributeType, new string[0]), arguments.ToArray());
			collection.Add(declaration);
			return declaration;
		}

		public CodeAttributeArgument CreateCodeAttributeArgument(Attribute attribute, PropertyInfo propertyInfo, object value)
		{
			CodeExpression propertyValue;
			if (propertyInfo.PropertyType == typeof(Type))
			{
				propertyValue = new CodeTypeOfExpression((Type)value);
			}
			else if (propertyInfo.PropertyType.IsEnum)
			{
				CodeTypeReferenceExpression typeReference = new CodeTypeReferenceExpression(propertyInfo.PropertyType);
				propertyValue = new CodeFieldReferenceExpression(typeReference, value.ToString());
			}
			else if (propertyInfo.PropertyType.IsPrimitive)
			{
				propertyValue = new CodePrimitiveExpression(value);
			}
			else
			{
				propertyValue = new CodePrimitiveExpression(value.ToString());
			}
			if (propertyInfo.CanWrite)
			{
				return new CodeAttributeArgument(propertyInfo.Name, propertyValue);
			}
			if (propertyInfo.Name != "TypeId")
			{
				return new CodeAttributeArgument(propertyValue);
			}
			return null;
		}

		private CodeTypeReference GetFieldValueTypeReference(CodeNamespace codeNamespace, SPField field)
		{
			if (field.FieldValueType == null)
				return null;
			if (typeof(SPFieldMultiChoice).IsAssignableFrom(field.GetType()))
			{
				CodeTypeDeclaration codeTypeDeclaration = GenerateEnum(codeNamespace, field as SPFieldMultiChoice);
				return new CodeTypeReference(codeTypeDeclaration.Name);
			}
			if (field.FieldValueType.IsValueType && !field.Required)
			{
				return new CodeTypeReference(typeof (Nullable<>).MakeGenericType(field.FieldValueType));
			}
			return new CodeTypeReference(field.FieldValueType);
		}
	}
}